/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.rop.code;

import java.util.BitSet;

import com.android.dx.rop.type.Type;
import com.android.dx.rop.type.TypeList;
import com.android.dx.util.FixedSizeList;

/**
 * List of {@link RegisterSpec} instances.
 */
public final class RegisterSpecList extends FixedSizeList implements TypeList {

	/** {@code non-null;} no-element instance */
	public static final RegisterSpecList EMPTY = new RegisterSpecList(0);

	/**
	 * Makes a single-element instance.
	 * 
	 * @param spec
	 *            {@code non-null;} the element
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public static RegisterSpecList make(RegisterSpec spec) {
		RegisterSpecList result = new RegisterSpecList(1);
		result.set(0, spec);
		return result;
	}

	/**
	 * Makes a two-element instance.
	 * 
	 * @param spec0
	 *            {@code non-null;} the first element
	 * @param spec1
	 *            {@code non-null;} the second element
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1) {
		RegisterSpecList result = new RegisterSpecList(2);
		result.set(0, spec0);
		result.set(1, spec1);
		return result;
	}

	/**
	 * Makes a three-element instance.
	 * 
	 * @param spec0
	 *            {@code non-null;} the first element
	 * @param spec1
	 *            {@code non-null;} the second element
	 * @param spec2
	 *            {@code non-null;} the third element
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1,
			RegisterSpec spec2) {
		RegisterSpecList result = new RegisterSpecList(3);
		result.set(0, spec0);
		result.set(1, spec1);
		result.set(2, spec2);
		return result;
	}

	/**
	 * Makes a four-element instance.
	 * 
	 * @param spec0
	 *            {@code non-null;} the first element
	 * @param spec1
	 *            {@code non-null;} the second element
	 * @param spec2
	 *            {@code non-null;} the third element
	 * @param spec3
	 *            {@code non-null;} the fourth element
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1,
			RegisterSpec spec2, RegisterSpec spec3) {
		RegisterSpecList result = new RegisterSpecList(4);
		result.set(0, spec0);
		result.set(1, spec1);
		result.set(2, spec2);
		result.set(3, spec3);
		return result;
	}

	/**
	 * Constructs an instance. All indices initially contain {@code null}.
	 * 
	 * @param size
	 *            the size of the list
	 */
	public RegisterSpecList(int size) {
		super(size);
	}

	/** {@inheritDoc} */
	public Type getType(int n) {
		return get(n).getType().getType();
	}

	/** {@inheritDoc} */
	public int getWordCount() {
		int sz = size();
		int result = 0;

		for (int i = 0; i < sz; i++) {
			result += getType(i).getCategory();
		}

		return result;
	}

	/** {@inheritDoc} */
	public TypeList withAddedType(Type type) {
		throw new UnsupportedOperationException("unsupported");
	}

	/**
	 * Gets the indicated element. It is an error to call this with the index
	 * for an element which was never set; if you do that, this will throw
	 * {@code NullPointerException}.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which element
	 * @return {@code non-null;} the indicated element
	 */
	public RegisterSpec get(int n) {
		return (RegisterSpec) get0(n);
	}

	/**
	 * Returns a RegisterSpec in this list that uses the specified register, or
	 * null if there is none in this list.
	 * 
	 * @param reg
	 *            Register to find
	 * @return RegisterSpec that uses argument or null.
	 */
	public RegisterSpec specForRegister(int reg) {
		int sz = size();
		for (int i = 0; i < sz; i++) {
			RegisterSpec rs;

			rs = get(i);

			if (rs.getReg() == reg) {
				return rs;
			}
		}

		return null;
	}

	/**
	 * Returns the index of a RegisterSpec in this list that uses the specified
	 * register, or -1 if none in this list uses the register.
	 * 
	 * @param reg
	 *            Register to find
	 * @return index of RegisterSpec or -1
	 */
	public int indexOfRegister(int reg) {
		int sz = size();
		for (int i = 0; i < sz; i++) {
			RegisterSpec rs;

			rs = get(i);

			if (rs.getReg() == reg) {
				return i;
			}
		}

		return -1;
	}

	/**
	 * Sets the element at the given index.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which element
	 * @param spec
	 *            {@code non-null;} the value to store
	 */
	public void set(int n, RegisterSpec spec) {
		set0(n, spec);
	}

	/**
	 * Gets the minimum required register count implied by this instance. This
	 * is equal to the highest register number referred to plus the widest width
	 * (largest category) of the type used in that register.
	 * 
	 * @return {@code >= 0;} the required registers size
	 */
	public int getRegistersSize() {
		int sz = size();
		int result = 0;

		for (int i = 0; i < sz; i++) {
			RegisterSpec spec = (RegisterSpec) get0(i);
			if (spec != null) {
				int min = spec.getNextReg();
				if (min > result) {
					result = min;
				}
			}
		}

		return result;
	}

	/**
	 * Returns a new instance, which is the same as this instance, except that
	 * it has an additional element prepended to the original. Mutability of the
	 * result is inherited from the original.
	 * 
	 * @param spec
	 *            {@code non-null;} the new first spec (to prepend)
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public RegisterSpecList withFirst(RegisterSpec spec) {
		int sz = size();
		RegisterSpecList result = new RegisterSpecList(sz + 1);

		for (int i = 0; i < sz; i++) {
			result.set0(i + 1, get0(i));
		}

		result.set0(0, spec);
		if (isImmutable()) {
			result.setImmutable();
		}

		return result;
	}

	/**
	 * Returns a new instance, which is the same as this instance, except that
	 * its first element is removed. Mutability of the result is inherited from
	 * the original.
	 * 
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public RegisterSpecList withoutFirst() {
		int newSize = size() - 1;

		if (newSize == 0) {
			return EMPTY;
		}

		RegisterSpecList result = new RegisterSpecList(newSize);

		for (int i = 0; i < newSize; i++) {
			result.set0(i, get0(i + 1));
		}

		if (isImmutable()) {
			result.setImmutable();
		}

		return result;
	}

	/**
	 * Returns a new instance, which is the same as this instance, except that
	 * its last element is removed. Mutability of the result is inherited from
	 * the original.
	 * 
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public RegisterSpecList withoutLast() {
		int newSize = size() - 1;

		if (newSize == 0) {
			return EMPTY;
		}

		RegisterSpecList result = new RegisterSpecList(newSize);

		for (int i = 0; i < newSize; i++) {
			result.set0(i, get0(i));
		}

		if (isImmutable()) {
			result.setImmutable();
		}

		return result;
	}

	/**
	 * Returns a new instance, which contains a subset of the elements specified
	 * by the given BitSet. Indexes in the BitSet with a zero are included,
	 * while indexes with a one are excluded. Mutability of the result is
	 * inherited from the original.
	 * 
	 * @param exclusionSet
	 *            {@code non-null;} set of registers to exclude
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public RegisterSpecList subset(BitSet exclusionSet) {
		int newSize = size() - exclusionSet.cardinality();

		if (newSize == 0) {
			return EMPTY;
		}

		RegisterSpecList result = new RegisterSpecList(newSize);

		int newIndex = 0;
		for (int oldIndex = 0; oldIndex < size(); oldIndex++) {
			if (!exclusionSet.get(oldIndex)) {
				result.set0(newIndex, get0(oldIndex));
				newIndex++;
			}
		}

		if (isImmutable()) {
			result.setImmutable();
		}

		return result;
	}

	/**
	 * Returns an instance that is identical to this one, except that all
	 * register numbers are offset by the given amount. Mutability of the result
	 * is inherited from the original.
	 * 
	 * @param delta
	 *            the amount to offset the register numbers by
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public RegisterSpecList withOffset(int delta) {
		int sz = size();

		if (sz == 0) {
			// Don't bother making a new zero-element instance.
			return this;
		}

		RegisterSpecList result = new RegisterSpecList(sz);

		for (int i = 0; i < sz; i++) {
			RegisterSpec one = (RegisterSpec) get0(i);
			if (one != null) {
				result.set0(i, one.withOffset(delta));
			}
		}

		if (isImmutable()) {
			result.setImmutable();
		}

		return result;
	}

	/**
	 * Returns an instance that is identical to this one, except that all
	 * incompatible register numbers are renumbered sequentially from the given
	 * base, with the first number duplicated if indicated. If a null BitSet is
	 * given, it indicates all registers are compatible.
	 * 
	 * @param base
	 *            the base register number
	 * @param duplicateFirst
	 *            whether to duplicate the first number
	 * @param compatRegs
	 *            {@code null-ok;} either a {@code non-null} set of compatible
	 *            registers, or {@code null} to indicate all registers are
	 *            compatible
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public RegisterSpecList withExpandedRegisters(int base,
			boolean duplicateFirst, BitSet compatRegs) {
		int sz = size();

		if (sz == 0) {
			// Don't bother making a new zero-element instance.
			return this;
		}

		RegisterSpecList result = new RegisterSpecList(sz);

		for (int i = 0; i < sz; i++) {
			RegisterSpec one = (RegisterSpec) get0(i);
			boolean replace = (compatRegs == null) ? true : !compatRegs.get(i);

			if (replace) {
				result.set0(i, one.withReg(base));
				if (!duplicateFirst) {
					base += one.getCategory();
				}
			} else {
				result.set0(i, one);
			}

			if (duplicateFirst) {
				duplicateFirst = false;
			}
		}

		if (isImmutable()) {
			result.setImmutable();
		}

		return result;
	}
}
